<?php

if (!class_exists('NotImplementedError')) {
    class    NotImplementedError extends Exception
    {
    }    // class NotImplementedError
}

if (!class_exists('RuntimeError')) {    
    class    RuntimeError extends Exception
    {
    }    // class RuntimeError
}

class    CachePacket
{
    const REQUEST  = 'REQUEST';
    const RESPONSE = 'RESPONSE';
    
    const INSERT = 'INSERT';
    const ERASE  = 'ERASE';
    const LOOKUP = 'LOOKUP';

    private $type;
    private $action;
    public  $key;
    public  $data;
    public  $timeout;
    public  $status;
         
    public function __construct()
    {
        $this->type = null;
        $this->action = null;
        $this->key = null;
        $this->data = null;
        $this->timeout = 0;
        $this->status = 0;    
    }
    
    public function isRequest()
    {
        return ($this->type == self::REQUEST);
    }
    public function isResponse()
    {
        return ($this->type == self::RESPONSE);
    }
    public function setRequest()
    {
        $this->type = self::REQUEST;
    }
    public function setResponse()
    {
        $this->type = self::RESPONSE;
    }
    
    public function isInsertAction()
    {
        return ($this->action == self::INSERT);
    }
    public function isEraseAction()
    {
        return ($this->action == self::ERASE);
    }
    public function isLookupAction()
    {
        return ($this->action == self::LOOKUP);
    }
    public function setInsertAction()
    {
        $this->action = self::INSERT;
    }
    public function setEraseAction()
    {
        $this->action = self::ERASE;
    }
    public function setLookupAction()
    {
        $this->action = self::LOOKUP;
    }
    
    const FIELD_SEPARATOR = "\t";
    const KV_SEPARATOR    = ':';

    public function encodeRequest()
    {
        if (!$this->isRequest()) {
            throw new RuntimeError('REQUEST packet wanted.');
        }
        
        $fields = array();
        $fields[] = join(self::KV_SEPARATOR, array('key', $this->key));
        if ($this->isInsertAction()) {
            $fields[] = join(self::KV_SEPARATOR, array('action','INSERT'));
            $fields[] = join(self::KV_SEPARATOR, array('data',$this->data));
            $fields[] = join(self::KV_SEPARATOR, array('expiration', strval($this->timeout)));
        }
        elseif ($this->isEraseAction()) {
            $fields[] = join(self::KV_SEPARATOR, array('action','ERASE'));
        }
        elseif ($this->isLookupAction()) {
            $fields[] = join(self::KV_SEPARATOR, array('action','LOOKUP'));
        }
        
        $record = join(self::FIELD_SEPARATOR, $fields);
        return base64_encode($record);
    }
    
    public function decodeRequest($record_base64)
    {
        $record = trim(base64_decode($record_base64));
        $fields = split(self::FIELD_SEPARATOR, $record);
        
        foreach ($fields as $field ) {
            list($k, $v) = split(self::KV_SEPARATOR, $field, 2);
            if (empty($v)) {
                throw new RuntimeError("Invalid field '$v' to decode.");
            }
            
            if ('action' == $k) {
                if ('INSERT' == $v) {
                    $this->setInsertAction();
                }
                elseif ('ERASE' == $v) {
                    $this->setEraseAction();
                }
                elseif ('LOOKUP' == $v) {
                    $this->setLookupAction();
                }
                else {
                    throw new RuntimeError("Invalid request action '$v'.");
                }
            }
            elseif ('key' == $k) {
                $this->key = $v;
            }
            elseif ('data' == $k) {
                $this->data = $v;
            }
            elseif ('expiration' == $k) {
                $this->timeout = intval($v);
            }
        }
        
        $this->setRequest();
    }
    
    public function encodeResponse()
    {
        if (!$this->isResponse()) {
            throw new RuntimeError('RESPONSE packet wanted.');
        }
        
        $fields = array();
        $fields[] = join(self::KV_SEPARATOR, array('key', $this->key));
        $fields[] = join(self::KV_SEPARATOR, array('status', strval($this->status)));
        if ($this->isInsertAction()) {
            $fields[] = join(self::KV_SEPARATOR, array('action','INSERT'));
            $fields[] = join(self::KV_SEPARATOR, array('expiration', strval($this->timeout)));
        }
        elseif ($this->isEraseAction()) {
            $fields[] = join(self::KV_SEPARATOR, array('action','ERASE'));
        }
        elseif ($this->isLookupAction()) {
            $fields[] = join(self::KV_SEPARATOR, array('action','LOOKUP'));
            if (!empty($this->data)) {
                $fields[] = join(self::KV_SEPARATOR, array('data',$this->data));
            }
        }
        
        $record = join(self::FIELD_SEPARATOR, $fields);
        return base64_encode($record);
    }
    
    public function decodeResponse($record_base64)
    {
        $record = trim(base64_decode($record_base64));
        $fields = split(self::FIELD_SEPARATOR, $record);
        
        foreach ($fields as $field ) {
            list($k, $v) = split(self::KV_SEPARATOR, $field, 2);
            if (empty($v)) {
                throw new RuntimeError("Invalid field '$v' to decode.");
            }
            
            if ('action' == $k) {
                if ('INSERT' == $v) {
                    $this->setInsertAction();
                }
                elseif ('ERASE' == $v) {
                    $this->setEraseAction();
                }
                elseif ('LOOKUP' == $v) {
                    $this->setLookupAction();
                }
                else {
                    throw new RuntimeError("Invalid request action '$v'.");
                }
            }
            elseif ('key' == $k) {
                $this->key = $v;
            }
            elseif ('data' == $k) {
                $this->data = $v;
            }
            elseif ('expiration' == $k) {
                $this->timeout = intval($v);
            }
            elseif ('status' == $k) {
                $this->status = intval($v);
            }
        }
        
        $this->setResponse();
    }
    
}    // class CachePacket

